/*
 * jxUtils Project (2014)
 * 
 * by Denilson Edinaldo Pinto
 * 
 * LGPL
 * Copyright (C) 2014
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.jxUtils;

import java.awt.Image;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.NClob;
import java.sql.Ref;
import java.sql.RowId;
import java.sql.SQLXML;
import java.sql.Struct;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.swing.Icon;
import javax.swing.ImageIcon;

import org.jxUtils.check.ParameterCheck;
import org.jxUtils.file.FileSize;

/**
 * @author Denilson Edinaldo Pinto
 * @porject jxUtils
 * @package org.jxUtils
 * @date 28/01/2014 - 19:57:35
 */
public final class TypeUtil {
	
	private static final Map<Class<?>, List<Class<?>>> PRIMITIVES = new HashMap<>(19);
	
	static {
		
		PRIMITIVES.put(BigDecimal.class, Arrays.asList(new Class<?>[]{BigDecimal.class}));
		PRIMITIVES.put(BigInteger.class, Arrays.asList(new Class<?>[]{BigInteger.class}));
		PRIMITIVES.put(Boolean.class, Arrays.asList(new Class<?>[]{Boolean.class, boolean.class}));
		PRIMITIVES.put(Byte.class, Arrays.asList(new Class<?>[]{Byte.class, byte.class}));
		PRIMITIVES.put(Calendar.class, Arrays.asList(new Class<?>[]{Calendar.class}));
		PRIMITIVES.put(Character.class, Arrays.asList(new Class<?>[]{Character.class, char.class}));
		PRIMITIVES.put(Date.class, Arrays.asList(new Class<?>[]{Date.class}));
		PRIMITIVES.put(java.sql.Date.class, Arrays.asList(new Class<?>[]{java.sql.Date.class}));
		PRIMITIVES.put(Double.class, Arrays.asList(new Class<?>[]{Double.class, double.class}));
		PRIMITIVES.put(Float.class, Arrays.asList(new Class<?>[]{Float.class, float.class}));
		PRIMITIVES.put(Integer.class, Arrays.asList(new Class<?>[]{Integer.class, int.class}));
		PRIMITIVES.put(Long.class, Arrays.asList(new Class<?>[]{Long.class, long.class}));
		PRIMITIVES.put(Number.class, Arrays.asList(new Class<?>[]{Number.class}));
		PRIMITIVES.put(Short.class, Arrays.asList(new Class<?>[]{Short.class, short.class}));
		PRIMITIVES.put(String.class, Arrays.asList(new Class<?>[]{String.class}));
		PRIMITIVES.put(Time.class, Arrays.asList(new Class<?>[]{Time.class}));
		PRIMITIVES.put(Timestamp.class, Arrays.asList(new Class<?>[]{Timestamp.class}));
		PRIMITIVES.put(URL.class, Arrays.asList(new Class<?>[]{URL.class}));
		PRIMITIVES.put(FileSize.class, Arrays.asList(new Class<?>[]{FileSize.class}));
	}
	
	/**
	 * @return the PRIMITIVES
	 */
	private static Map<Class<?>, List<Class<?>>> getPrimitives() {
	
		return PRIMITIVES;
	}
	
	/**
	 * @param clazz
	 * @return class wrapper
	 */
	public static Class<?> getPrimitiveWrapper(final Class<?> clazz) {
	
		ParameterCheck.isNull(clazz, "clazz");
		
		final Set<Class<?>> keys = getPrimitives().keySet();
		
		//
		for(final Class<?> key : keys) {
			
			//
			if(getPrimitives().get(key).contains(clazz)) {
				
				return key;
			}
		}
		
		return clazz;
	}
	
	/**
	 * @param clazz
	 * @return true if clazz is a Array
	 */
	public static boolean isArray(final Class<?> clazz) {
	
		return clazz == null ? false : clazz.isArray();
	}
	
	/**
	 * @param object
	 * @return true if object is a Array
	 */
	public static boolean isArray(final Object object) {
	
		return object == null ? false : isArray(object.getClass());
	}
	
	/**
	 * @param clazz
	 * @return true if clazz is Bean
	 */
	public static boolean isBean(final Class<?> clazz) {
	
		return clazz != null && 
			!clazz.isAnnotation() && 
			!clazz.isAnonymousClass() && 
			!clazz.isArray() && 
			!clazz.isEnum() && 
			!clazz.isInterface() && 
			!clazz.isPrimitive() && 
			!isCollection(clazz) && 
			!isMap(clazz) && 
			!isIcon(clazz) && 
			!isImageType(clazz) && 
			!isPrimitive(clazz) && 
			!clazz.equals(Class.class) && 
			!clazz.equals(Object.class);
	}
	
	/**
	 * @param object
	 * @return true if object is Bean
	 */
	public static boolean isBean(final Object object) {
	
		return object == null ? false : isBean(object.getClass());
	}
	
	/**
	 * @param clazz
	 * @return true if clazz is {@link Collection}
	 */
	public static boolean isCollection(final Class<?> clazz) {
	
		return clazz == null ? false : Collection.class.isAssignableFrom(clazz);
	}
	
	/**
	 * @param object
	 * @return true if object is {@link Collection}
	 */
	public static boolean isCollection(final Object object) {
	
		return object == null ? false : isCollection(object.getClass());
	}
	
	/**
	 * @param clazz
	 * @return true if clazz is {@link Date} or {@link Calendar}
	 */
	public static boolean isDate(final Class<?> clazz) {
	
		return clazz == null ? false : Date.class.isAssignableFrom(clazz) || Calendar.class.isAssignableFrom(clazz);
	}
	
	/**
	 * @param object
	 * @return true if object is {@link Date} or {@link Calendar}
	 */
	public static boolean isDate(final Object object) {
	
		return object == null ? false : isDate(object.getClass());
	}
	
	/**
	 * @param clazz
	 * @return true if clazz is {@link Date} or {@link Calendar} or {@link Time} or {@link Timestamp}
	 */
	public static boolean isDateOrTime(final Class<?> clazz) {
	
		return isTime(clazz) || isDate(clazz);
	}
	
	/**
	 * @param object
	 * @return true if object is {@link Date} or {@link Calendar} or {@link Time} or {@link Timestamp}
	 */
	public static boolean isDateOrTime(final Object object) {
	
		return object == null ? false : isDateOrTime(object.getClass());
	}
	
	/**
	 * @param clazz
	 * @return true if clazz is Enum
	 */
	public static boolean isEnum(final Class<?> clazz) {
	
		return clazz == null ? false : clazz.isEnum();
	}
	
	/**
	 * @param object
	 * @return true if object is Enum
	 */
	public static boolean isEnum(final Object object) {
	
		return object == null ? false : isEnum(object.getClass());
	}
	
	/**
	 * @param clazz
	 * @return true if clazz is {@link Icon}
	 */
	public static boolean isIcon(final Class<?> clazz) {
	
		return clazz == null ? false : Icon.class.isAssignableFrom(clazz);
	}
	
	/**
	 * @param object
	 * @return true if object is {@link Icon}
	 */
	public static boolean isIcon(final Object object) {
	
		return isIcon(object != null ? object.getClass() : null);
	}
	
	/**
	 * @param clazz
	 * @return true if clazz is {@link Image}
	 */
	public static boolean isImage(final Class<?> clazz) {
	
		return clazz == null ? false : Image.class.isAssignableFrom(clazz);
	}
	
	/**
	 * @param object
	 * @return true if object is {@link Image}
	 */
	public static boolean isImage(final Object object) {
	
		return isImage(object != null ? object.getClass() : null);
	}
	
	/**
	 * @param clazz
	 * @return true if clazz is {@link ImageIcon}
	 */
	public static boolean isImageIcon(final Class<?> clazz) {
	
		return clazz == null ? false : ImageIcon.class.isAssignableFrom(clazz);
	}
	
	/**
	 * @param object
	 * @return true if object is {@link ImageIcon}
	 */
	public static boolean isImageIcon(final Object object) {
	
		return isImageIcon(object != null ? object.getClass() : null);
	}
	
	/**
	 * @param clazz
	 * @return true if clazz is {@link ImageIcon} or {@link Image}
	 */
	public static boolean isImageType(final Class<?> clazz) {
	
		return isImageIcon(clazz) || isImage(clazz);
	}
	
	/**
	 * @param object
	 * @return true if object is {@link ImageIcon} or {@link Image}
	 */
	public static boolean isImageType(final Object object) {
	
		return isImageIcon(object) || isImage(object);
	}
	
	/**
	 * @param clazz
	 * @param object
	 * @return true if object is an instance of clazz
	 */
	public static boolean isInstance(final Class<?> clazz, final Object object) {
	
		return clazz != null && clazz.isInstance(object);
	}
	
	/**
	 * @param clazz
	 * @return true if clazz is {@link BigInteger} or {@link Long} or {@link Integer} or {@link Short}
	 */
	public static boolean isInteger(final Class<?> clazz) {
	
		return clazz != null && 
			(
				BigInteger.class.isAssignableFrom(clazz) || 
				Long.class.isAssignableFrom(clazz) || 
				Integer.class.isAssignableFrom(clazz) || 
				Short.class.isAssignableFrom(clazz) || 
				long.class.isAssignableFrom(clazz) || 
				int.class.isAssignableFrom(clazz) || 
				short.class.isAssignableFrom(clazz)
			);
	}
	
	/**
	 * @param object
	 * @return true if object is {@link BigInteger} or {@link Long} or {@link Integer} or {@link Short}
	 */
	public static boolean isInteger(final Object object) {
	
		return object == null ? false : isInteger(object.getClass());
	}
	
	/**
	 * @param clazz
	 * @return true if clazz is {@link Map}
	 */
	public static boolean isMap(final Class<?> clazz) {
	
		return clazz == null ? false : Map.class.isAssignableFrom(clazz);
	}
	
	/**
	 * @param object
	 * @return true if object is {@link Map}
	 */
	public static boolean isMap(final Object object) {
	
		return object == null ? false : isMap(object.getClass());
	}
	
	/**
	 * @param clazz
	 * @return true if clazz is {@link Number} or {@link #isInteger(Class)} or {@link #isRealNumber(Class)}
	 */
	public static boolean isNumber(final Class<?> clazz) {
	
		return clazz == null ? false : Number.class.isAssignableFrom(clazz) || isInteger(clazz) || isRealNumber(clazz);
	}
	
	/**
	 * @param object
	 * @return true if object class {@link #isNumber(Class)}
	 */
	public static boolean isNumber(final Object object) {
	
		return object == null ? false : isNumber(object.getClass());
	}
	
	/**
	 * @param clazz
	 * @return true if {@link #getPrimitives()} contains clazz
	 */
	public static boolean isPrimitive(final Class<?> clazz) {
	
		ParameterCheck.isNull(clazz, "clazz");
		
		//
		for(final List<Class<?>> classes : getPrimitives().values()) {
			
			//
			if(classes.contains(clazz)) {
				
				return true;
			}
		}
		
		return false;
	}
	
	/**
	 * @param clazz
	 * @return true if clazz {@link #isArray(Class)} and values of Array are Primitive
	 */
	public static boolean isPrimitiveArray(final Class<?> clazz) {
	
		ParameterCheck.isNull(clazz, "clazz");
		
		//
		if(isArray(clazz)) {
			
			final Class<?> type = clazz.getComponentType();
			
			return type != null ? type.isPrimitive() : false;
		}
		
		return false;
	}
	
	/**
	 * @param object
	 * @return true if object class {@link #isArray(Class)} and values of Array are Primitive
	 */
	public static boolean isPrimitiveArray(final Object object) {
	
		return object == null ? false : isPrimitiveArray(object.getClass());
	}
	
	/**
	 * @param clazz
	 * @return true if clazz is {@link BigDecimal} or {@link Double} or {@link Float}
	 */
	public static boolean isRealNumber(final Class<?> clazz) {
	
		return clazz != null && 
			(
				BigDecimal.class.isAssignableFrom(clazz) || 
				Double.class.isAssignableFrom(clazz) || 
				Float.class.isAssignableFrom(clazz) || 
				double.class.isAssignableFrom(clazz) || 
				float.class.isAssignableFrom(clazz)
			);
	}
	
	/**
	 * @param object
	 * @return true if object class {@link #isRealNumber(Class)}
	 */
	public static boolean isRealNumber(final Object object) {
	
		return object == null ? false : isRealNumber(object.getClass());
	}
	
	/**
	 * @param clazz
	 * @return true if clazz is {@link Time} or {@link Timestamp}
	 */
	public static boolean isTime(final Class<?> clazz) {
	
		return clazz == null ? false : Time.class.isAssignableFrom(clazz) || Timestamp.class.isAssignableFrom(clazz);
	}
	
	/**
	 * @param object
	 * @return true if object class {@link #isTime(Class)}
	 */
	public static boolean isTime(final Object object) {
	
		return object == null ? false : isTime(object.getClass());
	}
	
	/**
	 * @param object
	 * @return {@link Types}
	 */
	public static final int javaTypeToSqlType(final Object object) {
	
		//
		if(object == null) {
			
			return Types.OTHER;
			
		}else if(object instanceof BigDecimal || object.getClass().equals(BigDecimal.class) || object.equals(BigDecimal.class)) {
			
			return Types.NUMERIC;
			
		}else if(object instanceof Blob || object.getClass().equals(Blob.class) || object.equals(Blob.class)) {
			
			return Types.BLOB;
			
		}else if(object instanceof Boolean || object.getClass().equals(Boolean.class) || object.getClass().equals(boolean.class) || object.equals(Boolean.class) || object.equals(Boolean.class)) {
			
			return Types.BOOLEAN;
			
		}else if(object instanceof Byte || object.getClass().equals(Byte.class) || object.getClass().equals(byte.class) || object.equals(Byte.class) || object.equals(byte.class)) {
			
			return Types.TINYINT;
			
		}else if(object instanceof Byte[] || object.getClass().equals(Byte[].class) || object.getClass().equals(byte[].class) || object.equals(Byte.class) || object.equals(byte[].class)) {
			
			return Types.LONGVARBINARY;
			
		}else if(object instanceof Clob || object.getClass().equals(Clob.class) || object.equals(Clob.class)) {
			
			return Types.CLOB;
			
		}else if(object instanceof Double || object.getClass().equals(Double.class) || object.getClass().equals(double.class) || object.equals(Double.class) || object.equals(double.class)) {
			
			return Types.DOUBLE;
			
		}else if(object instanceof Float || object.getClass().equals(Float.class) || object.getClass().equals(float.class) || object.equals(Float.class) || object.equals(float.class)) {
			
			return Types.FLOAT;
			
		}else if(object instanceof Integer || object.getClass().equals(Integer.class) || object.getClass().equals(int.class) || object.equals(Integer.class) || object.equals(int.class)) {
			
			return Types.INTEGER;
			
		}else if(object instanceof BigInteger || object instanceof Long || object.getClass().equals(BigInteger.class) || object.getClass().equals(Long.class) || object.getClass().equals(long.class) || object.equals(BigInteger.class) || object.equals(Long.class) || object.equals(long.class)) {
			
			return Types.BIGINT;
			
		}else if(object instanceof NClob || object.getClass().equals(NClob.class) || object.equals(NClob.class)) {
			
			return Types.NCLOB;
			
		}else if(object instanceof Ref || object.getClass().equals(Ref.class) || object.equals(Ref.class)) {
			
			return Types.REF;
			
		}else if(object instanceof RowId || object.getClass().equals(RowId.class) || object.equals(RowId.class)) {
			
			return Types.ROWID;
			
		}else if(object instanceof Short || object.getClass().equals(Short.class) || object.getClass().equals(short.class) || object.equals(Short.class) || object.equals(short.class)) {
			
			return Types.SMALLINT;
			
		}else if(object instanceof SQLXML || object.getClass().equals(SQLXML.class) || object.equals(SQLXML.class)) {
			
			return Types.SQLXML;
			
		}else if(object instanceof String || object.getClass().equals(String.class) || object.equals(String.class)) {
			
			return Types.LONGVARCHAR;
			
		}else if(object instanceof Time || object.getClass().equals(Time.class) || object.equals(Time.class)) {
			
			return Types.TIME;
			
		}else if(object instanceof Timestamp || object.getClass().equals(Timestamp.class) || object.equals(Timestamp.class)) {
			
			return Types.TIMESTAMP;
			
		}else if(object instanceof java.sql.Date || object instanceof Date || object.getClass().equals(java.sql.Date.class) || object.getClass().equals(Date.class) || object.equals(java.sql.Date.class) || object.equals(Date.class)) {
			
			return Types.DATE;
			
		}else if(object instanceof URL || object.getClass().equals(URL.class) || object.equals(URL.class)) {
			
			return Types.DATALINK;
			
		}else if(object instanceof Object[] || object.getClass().equals(Object[].class) || object.equals(Object[].class)) {
			
			return Types.ARRAY;
			
		}else {
			
			return Types.OTHER;
		}
	}
	
	/**
	 * @param sqlType
	 * @see {@link Types}
	 * @return Java Class or null
	 */
	public static final Class<?> sqlTypeToJavaType(final int sqlType) {
	
		switch(sqlType) {
		
			case Types.ARRAY:
				
				return Object[].class;
				
			case Types.BIGINT:
				
				return Long.class;
				
			case Types.BINARY:
				
				return Byte[].class;
				
			case Types.BIT:
				
				return Boolean.class;
				
			case Types.BLOB:
				
				return Blob.class;
				
			case Types.BOOLEAN:
				
				return Boolean.class;
				
			case Types.CHAR:
				
				return String.class;
				
			case Types.CLOB:
				
				return Clob.class;
				
			case Types.DATALINK:
				
				return URL.class;
				
			case Types.DATE:
				
				return java.sql.Date.class;
				
			case Types.DECIMAL:
				
				return BigDecimal.class;
				
			case Types.DISTINCT:
				
				return Object.class;
				
			case Types.DOUBLE:
				
				return Double.class;
				
			case Types.FLOAT:
				
				return Float.class;
				
			case Types.INTEGER:
				
				return Integer.class;
				
			case Types.JAVA_OBJECT:
				
				return Object.class;
				
			case Types.LONGNVARCHAR:
				
				return String.class;
				
			case Types.LONGVARBINARY:
				
				return Byte[].class;
				
			case Types.LONGVARCHAR:
				
				return String.class;
				
			case Types.NCHAR:
				
				return String.class;
				
			case Types.NCLOB:
				
				return NClob.class;
				
			case Types.NULL:
				
				return Object.class;
				
			case Types.NUMERIC:
				
				return BigDecimal.class;
				
			case Types.NVARCHAR:
				
				return String.class;
				
			case Types.OTHER:
				
				return Object.class;
				
			case Types.REAL:
				
				return BigDecimal.class;
				
			case Types.REF:
				
				return Ref.class;
				
			case Types.ROWID:
				
				return RowId.class;
				
			case Types.SMALLINT:
				
				return Short.class;
				
			case Types.SQLXML:
				
				return SQLXML.class;
				
			case Types.STRUCT:
				
				return Struct.class;
				
			case Types.TIME:
				
				return Time.class;
				
			case Types.TIMESTAMP:
				
				return Timestamp.class;
				
			case Types.TINYINT:
				
				return Short.class;
				
			case Types.VARBINARY:
				
				return Byte[].class;
				
			case Types.VARCHAR:
				
				return String.class;
				
			default:
				
				return null;
		}
	}
}
